﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Aphysoft.Vate.Graphics
{
    public static class Line
    {
        #region Line struct
        /// <summary>
        /// Struct for a line, instances of this class will be added to lines.
        /// </summary>
        struct LineEntry
        {
            // Positions
            public Vector3 startPoint, endPoint;
            // Colors
            public Color startColor, endColor;

            /// <summary>
            /// Constructor
            /// </summary>
            public LineEntry(
                Vector3 setStartPoint, Color setStartColor,
                Vector3 setEndPoint, Color setEndColor)
            {
                startPoint = setStartPoint;
                startColor = setStartColor;
                endPoint = setEndPoint;
                endColor = setEndColor;
            }

            /// <summary>
            /// Are these two Lines equal?
            /// </summary>
            public static bool operator ==(LineEntry a, LineEntry b)
            {
                return
                    a.startPoint == b.startPoint &&
                    a.endPoint == b.endPoint &&
                    a.startColor == b.startColor &&
                    a.endColor == b.endColor;
            }

            /// <summary>
            /// Are these two Lines not equal?
            /// </summary>
            public static bool operator !=(LineEntry a, LineEntry b)
            {
                return
                    a.startPoint != b.startPoint ||
                    a.endPoint != b.endPoint ||
                    a.startColor != b.startColor ||
                    a.endColor != b.endColor;
            }

            /// <summary>
            /// Support Equals(.) to keep the compiler happy
            /// (because we used == and !=)
            /// </summary>
            public override bool Equals(object a)
            {
                if (a is LineEntry)
                    return (LineEntry)a == this;

                return false;
            }

            /// <summary>
            /// Support GetHashCode() to keep the compiler happy
            /// (because we used == and !=)
            /// </summary>
            public override int GetHashCode()
            {
                return 0; // Not supported or nessescary
            }
        }
        #endregion

        #region Constants
        /// <summary>
        /// Max. number of lines allowed to prevent to big buffer, will never
        /// be reached, but in case something goes wrong or numOfLines is not
        /// reseted each frame, we won't add unlimited lines (all new lines
        /// will be ignored if this max. number is reached).
        /// </summary>
        internal const int MaxNumOfLines = 4096;
        #endregion

        #region Properties
        /// <summary>
        /// Number of lines used this frame, will be set to 0 when rendering.
        /// </summary>
        private static int numOfLines = 0;
        /// <summary>
        /// The actual list for all the lines, it will NOT be reseted each
        /// frame like numOfLines! We will remember the last lines and
        /// only change this list when anything changes (new line, old
        /// line missing, changing line data).
        /// When this happens buildVertexBuffer will be set to true.
        /// </summary>
        private static List<LineEntry> lines = new List<LineEntry>();
        /// <summary>
        /// Build vertex buffer this frame because the line list was changed?
        /// </summary>
        private static bool buildVertexBuffer = false;
        /// <summary>
        /// Vertex buffer for all lines
        /// </summary>
        private static VertexPositionColor[] lineVertices = new VertexPositionColor[MaxNumOfLines * 2];
        /// <summary>
        /// Real number of primitives currently used.
        /// </summary>
        private static int numOfPrimitives = 0;
        /// <summary>
        /// Vertex declaration for our lines.
        /// </summary>
        private static VertexDeclaration decl = null;
        /// <summary>
        /// True if Line manager has been inited; otherwise, false.
        /// </summary>
        private static bool inited = false;
        #endregion

        #region Initiator
        /// <summary>
        /// Initializes Line manager.
        /// </summary>
        public static void Init()
        {
            if (inited)
                throw new Exception("Line manager has been initialized.");
            else
                inited = true;

            decl = new VertexDeclaration(Vate.Device, VertexPositionColor.VertexElements);

#if DEBUG
            Debug.Write("Line manager Initialized.");
#endif
        }

        #endregion

        #region Dispose
        /// <summary>
        /// Dispose.
        /// </summary>
        internal static void Dispose()
        {
            decl.Dispose();
        }
        #endregion

        #region Methods
        /// <summary>
        /// Draw a line.
        /// </summary>
        /// <param name="startPoint">Start vector point.</param>
        /// <param name="startColor">Color of start vector point.</param>
        /// <param name="endPoint">End vector point.</param>
        /// <param name="endColor">Color of end vector point.</param>
        public static void Draw(Vector3 startPoint, Color startColor, Vector3 endPoint, Color endColor)
        {
            // Don't add new lines if limit is reached
            if (numOfLines >= MaxNumOfLines)
            {
                /*ignore
                Log.Write("Too many lines requested in LineManager3D. " +
                    "Max lines = " + MaxNumOfLines);
                 */
                return;
            }

            // Build line
            LineEntry line = new LineEntry(startPoint, startColor, endPoint, endColor);

            // Check if this exact line exists at the current lines position.
            if (lines.Count > numOfLines)
            {
                if ((LineEntry)lines[numOfLines] != line)
                {
                    // overwrite old line, otherwise just increase numOfLines
                    lines[numOfLines] = line;
                    // Remember to build vertex buffer in Render()
                    buildVertexBuffer = true;
                }
            }
            else
            {
                // Then just add new line
                lines.Add(line);
                // Remember to build vertex buffer in Render()
                buildVertexBuffer = true;
            }

            // nextUpValue line
            numOfLines++;
        }
        /// <summary>
        /// Draw a line.
        /// </summary>
        /// <param name="startPoint">Start vector point.</param>
        /// <param name="endPoint">End vector point.</param>
        /// <param name="color">Color of line.</param>
        public static void Draw(Vector3 startPoint, Vector3 endPoint, Color color)
        {
            Draw(startPoint, color, endPoint, color);
        }
        #endregion

        #region Update vertex buffer
        private static void UpdateVertexBuffer()
        {
            // Don't do anything if we got no lines. Or if some data is invalid
            if (numOfLines == 0 || lines.Count < numOfLines)
            {
                numOfPrimitives = 0;
                return;
            }

            // Set all lines
            for (int lineNum = 0; lineNum < numOfLines; lineNum++)
            {
                LineEntry line = (LineEntry)lines[lineNum];
                lineVertices[lineNum * 2 + 0] = new VertexPositionColor(line.startPoint, line.startColor);
                lineVertices[lineNum * 2 + 1] = new VertexPositionColor(line.endPoint, line.endColor);
            }
            numOfPrimitives = numOfLines;

            // Vertex buffer was build
            buildVertexBuffer = false;
        }
        #endregion

        #region Draw
        /// <summary>
        /// Render all lines added this frame
        /// </summary>
        public static void Draw()
        {
            // Need to build vertex buffer?
            if (buildVertexBuffer || numOfPrimitives != numOfLines)
            {
                UpdateVertexBuffer();
            }

            // Render lines if we got any lines to render
            if (numOfPrimitives > 0)
            {
                Shader.LineRendering.Draw(
                    "LineRendering3D",
                    delegate
                    {
                        Vate.Device.RenderState.AlphaBlendEnable = true;
                        Vate.Device.RenderState.SourceBlend = Blend.SourceAlpha;
                        Vate.Device.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
                        Vate.Device.VertexDeclaration = decl;
                        Vate.Device.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, lineVertices, 0, numOfPrimitives);
                    });
            }

            // Ok, finally reset numOfLines for next frame
            numOfLines = 0;
        }
        #endregion
    }
}
